/**
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2022. All rights reserved.
 */
#include "dvpp_cmdlist_proc.h"
#include <linux/types.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/version.h>
#include "securec.h"
#include "dvpp_cmdlist_log.h"
#include "dvpp_cmdlist_context.h"

#define PROC_NAME "dvpp_cmdlist"
#define PROC_BUF_LEN 128

static void disaply_time_cost(struct seq_file *m)
{
    uint16_t i = 0;
    dvpp_cmdlist_context *ctx = get_dvpp_cmdlist_ctx();
    time_cost_node* pos = NULL;
    struct hlist_node* n = NULL;
    struct hlist_head* head = NULL;
    seq_printf(m, "Time Cost Enable = %d\n", ctx->time_cost_stat.enable);
    seq_printf(m, "Time Cost Node Num = %d\n", ctx->time_cost_stat.node_num);
    seq_printf(m, "Time Cost Details(us):\n");
    seq_printf(m, "%10s"
        "%10s"
        "%18s"
        "%18s"
        "%18s"
        "%18s"
        "%18s"
        "\n",
        "[StreamId]", "[TaskId]", "[UsrCopyToKern]", "[Vmalloc]", "[DmaAlloc]", "[U2kMap]", "[PcieCopy]");

    spin_lock(&ctx->time_cost_stat.splock);
    for (i = 0; i < ctx->time_cost_stat.node_num; i++) {
        head = &ctx->time_cost_stat.head[i];
        hlist_for_each_entry_safe(pos, n, head, hlist) {
            seq_printf(m, "%10d"
                "%10d"
                "%18lld"
                "%18lld"
                "%18lld"
                "%18lld"
                "%18lld"
                "\n",
                pos->streamid,
                pos->taskid,
                pos->time_cost_usr_copy_to_kern,
                pos->time_cost_vmalloc,
                pos->time_cost_dma_alloc,
                pos->time_cost_u2k_map,
                pos->time_cost_pcie_copy);
        }
    }
    spin_unlock(&ctx->time_cost_stat.splock);
}

static void clear_time_cost(void)
{
    uint16_t i = 0;
    time_cost_node* pos = NULL;
    struct hlist_node* n = NULL;
    struct hlist_head* head = NULL;
    dvpp_cmdlist_context *ctx = get_dvpp_cmdlist_ctx();

    spin_lock(&ctx->time_cost_stat.splock);
    for (i = 0; i < MAX_TIME_COST_NODE_LEN; i++) {
        head = &ctx->time_cost_stat.head[i];
        hlist_for_each_entry_safe(pos, n, head, hlist) {
            hlist_del(&pos->hlist);
        }
    }
    spin_unlock(&ctx->time_cost_stat.splock);
}

static int dvpp_cmdlist_proc_show(struct seq_file *m, void *v)
{
    seq_printf(m, "Welcome To Dvpp Cmldist Proc.\n");
    disaply_time_cost(m);
    seq_printf(m, "\n");

    return 0;
}

/* delete all blanks in a line */
static void strip_string(char * str, char *res)
{
    char *s = str;
    char *p = res;
    do {
        if (*s == '\n') {
            *s = '\0';
        }
        if (*s != ' ') {
            *p++ = *s;
        }
    } while (*s++ != '\0');
}

static int32_t str2num(const char* str, uint32_t *num)
{
    const uint32_t max_len = 8; // 先限制右边字符串长度为8，再转换
    uint32_t str_len = (uint32_t)strlen(str); // right为等号右边的字符串
    char *endptr = NULL;
    if (str_len > max_len) {
        DVPP_CMDLIST_VPC_LOG_ERROR("the lenght of string %u cannot be larger than %u \n",
            str_len, max_len);
        return -1;
    }

    *num = (uint32_t)(simple_strtoul(str, &endptr, 10)); // 转化成10进制
    if ((uint32_t)(((uintptr_t)endptr - (uintptr_t)str)) != str_len) {
        DVPP_CMDLIST_VPC_LOG_ERROR("num value is unknown. Please input num 0 - 9 \n");
        return -1;
    }

    return 0;
}

static int32_t hanle_left_str_and_right_num(char *buf, char *target, uint64_t len, uint32_t *num)
{
    uint8_t left[PROC_BUF_LEN] = {0};
    uint8_t right[PROC_BUF_LEN] = {0};
    int32_t ret = sscanf_s(buf, "%[^=]=%s", left, len, right, len);
    if (ret != 2) { // 如果没有按格式输入2个字符串
        DVPP_CMDLIST_VPC_LOG_ERROR("cmd format is error, please check.\n");
        return -1;
    }
    if (strcmp(target, left) == 0) {
        // 长度检查，过滤数字，并转成10进制u64
        ret = str2num(right, num);
        if (ret != 0) {
            return -1;
        }

        return 0;
    }

    DVPP_CMDLIST_VPC_LOG_ERROR("cmd format is error, please check.\n");
    return -1;
}

static ssize_t dvpp_cmdlist_proc_write(struct file *file, const char __user *buffer, size_t count, loff_t *pos)
{
    dvpp_cmdlist_context *ctx = get_dvpp_cmdlist_ctx();
    uint8_t buf[PROC_BUF_LEN] = {0};
    uint8_t strip_buf[PROC_BUF_LEN] = {0};
    ssize_t length;

    if (count < 1) {
        return -EINVAL;
    }

    if (buffer == NULL) {
        return -EINVAL;
    }

    length = (ssize_t)(count > PROC_BUF_LEN ? PROC_BUF_LEN : count);
    if (copy_from_user(&buf[0], buffer, (uint32_t)(length))) {
        return -EFAULT;
    }
    buf[length - 1] = '\0';
    strip_string(buf, strip_buf);

    if (strcmp("enable_time_cost", strip_buf) == 0) {
        ctx->time_cost_stat.enable = 1;
        return length;
    }

    if (strcmp("disable_time_cost", strip_buf) == 0) {
        ctx->time_cost_stat.enable = 0;
        return length;
    }

    if (strcmp("clear_time_cost", strip_buf) == 0) {
        clear_time_cost();
        return length;
    }

    (void)hanle_left_str_and_right_num(strip_buf, "time_cost_node", (uint64_t)(length), &ctx->time_cost_stat.node_num);

    if (ctx->time_cost_stat.node_num > MAX_TIME_COST_NODE_LEN) {
        ctx->time_cost_stat.node_num = MAX_TIME_COST_NODE_LEN;
        DVPP_CMDLIST_VPC_LOG_WARN("max time cost node num is %d.\n", MAX_TIME_COST_NODE_LEN);
    }

    return length;
}


static int dvpp_cmdlist_proc_open(struct inode *inode, struct file *file)
{
    return single_open(file, dvpp_cmdlist_proc_show, NULL);
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)
static const struct proc_ops dvpp_cmdlist_proc_ops = {
    .proc_open      = dvpp_cmdlist_proc_open,
    .proc_read      = seq_read,
    .proc_lseek     = seq_lseek,
    .proc_release   = single_release,
    .proc_write     = dvpp_cmdlist_proc_write,
};
#else
static struct file_operations dvpp_cmdlist_proc_ops = {
    .owner = THIS_MODULE,
    .open = dvpp_cmdlist_proc_open,
    .read = seq_read,
    .write = dvpp_cmdlist_proc_write,
    .llseek = seq_lseek,
    .release = single_release
};
#endif

void dvpp_cmdlist_init_proc(void)
{
    proc_create(PROC_NAME, 0, NULL, &dvpp_cmdlist_proc_ops);
}

void dvpp_cmdlist_remove_proc(void)
{
    remove_proc_entry(PROC_NAME, NULL);
}